local easing = require "easing"

local Mix = Class(function(self, name)
    self.name = name or ""
    self.levels = {}
    self.priority = 0
    self.fadeintime = 1
end)

function Mix:__tostring()
    local t = {}
    
    for k,v in pairs(self.levels) do
        table.insert(t, string.format("%s:%2.2f", k, v))
    end
    return string.format("%s = pri:%2.2f, fade:%2.2f, levels:[%s]", self.name, self.priority, self.fadeintime, table.concat(t, ",") or "")
    
end

function Mix:Apply()
    for k,v in pairs(self.levels) do
        TheSim:SetSoundVolume(k, v)
    end
end

function Mix:SetLevel(channel, level)
    self.levels[channel] = level
end

function Mix:GetLevel(channel)
	local val = self.levels[channel] or 0
    return val
end

local Mixer = Class(function(self)
    self.mixes = {}
    self.stack = {}
    
    self.lowpassfilters = {}
end)

function Mixer:AddNewMix(name, fadetime, priority, levels)
    local mix = Mix(name)
    mix.fadeintime = fadetime or 1
    mix.priority = priority or 0
    
    self.mixes[name] = mix
    for k,v in pairs(levels) do
        mix:SetLevel(k, v)
    end
    
    return mix
end

function Mixer:CreateSnapshot()
    
    local top = self.stack[1]
    if top then
        local snap = Mix()
        for k,v in pairs(top.levels) do
            snap:SetLevel(k, TheSim:GetSoundVolume(k))
        end
        return snap
    end
end

function Mixer:Blend()
    self.snapshot = self:CreateSnapshot()
    self.fadetimer = 0
end

function Mixer:GetLevel(lev)
	local val = TheSim:GetSoundVolume(lev)
    return val
end

function Mixer:SetLevel(name, lev)
    TheSim:SetSoundVolume(name, lev)
end

function Mixer:Update(dt)
    
    local top = self.stack[1]
    if self.snapshot and top then
        self.fadetimer = self.fadetimer + dt
        local lerp = self.fadetimer / top.fadeintime
        
        if lerp > 1 then
            self.snapshot = nil
            top:Apply()
        else
            for k,v in pairs(self.snapshot.levels) do
                local lev = easing.linear(self.fadetimer, v, top:GetLevel(k) - v, top.fadeintime)
                TheSim:SetSoundVolume(k, lev)
            end
        end
    end
    
    self:UpdateFilters(dt)
end

function Mixer:PopMix(mixname)
    local top = self.stack[1]
    for k, v in ipairs(self.stack) do
        
        
        if mixname == v.name then
            table.remove(self.stack, k)
            if top ~= self.stack[1] then
                self:Blend()
            end
            break
        end
    end
end


function Mixer:PushMix(mixname)
    
    local mix = self.mixes[mixname]
    
    local current = self.stack[1]
    
    if mix then
        table.insert(self.stack, mix)
        table.sort(self.stack, function(l, r) return l.priority > r.priority end)
        
        if current and current ~= self.stack[1] then
            self:Blend()
        elseif not current then
            mix:Apply()
        end
        
    end
end

local top_val = 25000

function Mixer:UpdateFilters(dt)
	for k,v in pairs(self.lowpassfilters) do
		if v.totaltime > 0 and v.currenttime < v.totaltime then
			v.currenttime = v.currenttime + dt
			v.freq = easing.linear(v.currenttime, v.startfreq, v.endfreq - v.startfreq, v.totaltime)
			TheSim:SetLowPassFilter(k, v.freq)
		elseif v.freq >= top_val then
			TheSim:ClearDSP(k)
			self.lowpassfilters[k] = nil
		end
	end
	
end



function Mixer:SetLowPassFilter(category, cutoff, timetotake)
	timetotake = timetotake or 3
	
	local startfreq = top_val
	if self.lowpassfilters[category] then
		startfreq = self.lowpassfilters[category].freq
	end
	
	local freq_entry = {startfreq = startfreq, endfreq = cutoff, freq= startfreq, totaltime = timetotake, currenttime = 0}
	self.lowpassfilters[category] = freq_entry
	
	if timetotake <= 0 then
		freq_entry.freq = cutoff
		TheSim:SetLowPassFilter(category, cutoff)
	end
	
	
end

function Mixer:ClearLowPassFilter(category, timetotake)
	self:SetLowPassFilter(category, top_val, timetotake)	
end

return { Mix = Mix, Mixer = Mixer}
